﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DotNetCommon
{
#if NET5_0_OR_GREATER
    /// <summary>
    /// 进程内异步锁 (由于lock关键字、Monitor无法对异步代码块加锁，所以封装了此类，它基于 <seealso cref="SemaphoreSlim"/> 实现)，使用示例：<br />
    /// <list type="number">
    /// <item> 锁代码块
    /// <code>
    /// await AsyncLocker.LockAsync("123", async () =>
    /// {
    ///     //somecode ...
    /// });
    /// </code>
    /// </item>
    /// <item> 使用 using
    /// <code>
    /// await using var lockObj = await AsyncLocker.LockAsync("123");
    /// //somecode...
    /// </code>
    /// </item>
    /// </list>
    /// </summary>
    public class AsyncLocker : IAsyncDisposable
    {
        private static readonly ConcurrentDictionary<string, SemaphoreSlim> _lockDic = new ConcurrentDictionary<string, SemaphoreSlim>();
        private readonly SemaphoreSlim _currentLock;
        private AsyncLocker(string key)
        {
            _currentLock = _lockDic.GetOrAdd(key, key => new SemaphoreSlim(1, 1));
        }

        /// <summary>
        /// 将锁加到 using 块中，如：<code>await using var lockObj = await AsyncLocker.LockAsync("123");</code>
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static async ValueTask<AsyncLocker> LockAsync(string key)
        {
            var locker = new AsyncLocker(key);
            await locker._currentLock.WaitAsync();
            return locker;
        }

        /// <summary>
        /// 无返回值的锁
        /// </summary>
        /// <param name="lockStr"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static async Task LockAsync(string lockStr, Func<Task> func)
        {
            await using var locker = new AsyncLocker(lockStr);
            await locker._currentLock.WaitAsync();
            await func();
        }

        /// <summary>
        /// 有返回值的锁
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="lockStr"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static async Task<T> LockAsync<T>(string lockStr, Func<Task<T>> func)
        {
            await using var locker = new AsyncLocker(lockStr);
            await locker._currentLock.WaitAsync();
            return await func();
        }

        private bool _disposed = false;
        /// <summary>
        /// 释放锁
        /// </summary>
        /// <returns></returns>
        public ValueTask DisposeAsync()
        {
            if (!_disposed)
            {
                _currentLock.Release();
                _disposed = true;
            }
            return ValueTask.CompletedTask;
        }
    }
#endif

#if NET47 || NETCOREAPP3_1 || NETSTANDARD2_0
    /// <summary>
    /// 进程内异步锁 (由于lock关键字、Monitor无法对异步代码块加锁，所以封装了此类，它基于 <seealso cref="SemaphoreSlim"/> 实现)，使用示例：<br />
    /// <code>
    /// await AsyncLocker.LockAsync("123", async () =>
    /// {
    ///     //somecode ...
    /// });
    /// </code>
    /// </summary>
    public class AsyncLocker : IDisposable
    {
        private static readonly ConcurrentDictionary<string, SemaphoreSlim> _lockDic = new ConcurrentDictionary<string, SemaphoreSlim>();
        private readonly SemaphoreSlim _currentLock;
        private AsyncLocker(string key)
        {
            _currentLock = _lockDic.GetOrAdd(key, key => new SemaphoreSlim(1, 1));
        }

        /// <summary>
        /// 无返回值的异步锁
        /// </summary>
        /// <param name="lockStr"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static async Task LockAsync(string lockStr, Func<Task> func)
        {
            using var locker = new AsyncLocker(lockStr);
            await locker._currentLock.WaitAsync();
            await func();
        }

        /// <summary>
        /// 有返回值的异步锁
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="lockStr"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static async Task<T> LockAsync<T>(string lockStr, Func<Task<T>> func)
        {
            using var locker = new AsyncLocker(lockStr);
            await locker._currentLock.WaitAsync();
            return await func();
        }

        private bool _disposed = false;
        /// <summary>
        /// 释放锁
        /// </summary>
        public void Dispose()
        {
            if (!_disposed)
            {
                _currentLock.Release();
                _disposed = true;
            }
        }
    }
#endif


}
